from sqlalchemy import *
from sqlalchemy.orm import *

from Note import *
from Page import *
import os, re
from datetime import datetime
import time

class NoteModel(object):
  """The CNote class."""
  def __init__(self, db_name=None):
    self.db_name = db_name
    if db_name:
      metadata = MetaData('sqlite:///%s' % (db_name))
    else:
      metadata = MetaData('sqlite://')
    #metadata.engine.echo = True

    notes_table = Table('notes', metadata,
      Column('id', Integer, primary_key=True),
      Column('trashed', Integer, default=0 ),
    )
    
    pages_table = Table('pages', metadata,
      Column('id', Integer, primary_key=True),
      Column('content', Unicode),
      Column('created_at', Integer, default=int(time.time()) ),
      Column('note_id', Integer, ForeignKey('notes.id'), index=True)
    )
    

    # create a database if not already exists
    if db_name:
      if not os.path.exists(db_name):
        notes_table.create()
        pages_table.create()
    else:
      notes_table.create()
      pages_table.create()


    mapper(Page, pages_table)
    
    
    mapper(Note, notes_table, properties={
      'pages': relation(Page)
    })

    
    self.session = create_session()

  def patch_content(self, content, revision):
    return content

  # now, get the revisions
  def revisions(self, note, current = False):

    revisions = note.pages
    last_revision = len(revisions)
  
    notes = []
    
    revision_no = 1

    
    # optimization
    if current:
      revisions = [note.pages[-1]]
      revision_no = last_revision
    
    for revision in revisions:
      timestamp = revision.created_at
      content = revision.content
      notes.append( {'revision' : revision_no, 'content' : content, 'timestamp' : timestamp, 'id' : note.id, 'last_revision' : last_revision })
      revision_no += 1
      
    notes.reverse()
    
    return notes

  def add(self, content):
    note = Note()
    note.pages.append(Page(content))
    self.session.save(note)
    self.session.flush()
    
    return self.humanize(note)


  def humanize(self, note):
    return self.get(note.id)

  def get(self, id):
    return self.history(id, revision=0)[0]

  def query(self, keywords = None, pagination = 10, trashed = False):
    
    if keywords:
      try:
        keywords = keywords.replace(r'\ ', r'\space')
        tokens = keywords.split()
        tokens = [token.replace(r'\space', r' ') for token in tokens]
        patterns = []
        for token in tokens:
          if ( token.startswith('/') or token.startswith(u'/') ) and ( token.endswith('/') or token.endswith(u'/') ):
            pattern = re.compile(token[1:-1], re.I)
          else:
            pattern = token
          patterns.append(pattern)
      except:
        return []
    
    query = self.session.query(Note)
    
    if trashed:
      notes = query.filter_by(trashed=1)
    else:
      notes = query.filter_by(trashed=0)

    # results: [ {revision, content, timestamp, trashed, id}, ]
    results = []

    for note in notes:
      note_last = self.revisions(note, current=True)[0]
      note_last['trashed'] = note.trashed
      if keywords:
        all_found = True
        for pattern in patterns:
          if not ( type(pattern) is str or type(pattern) is unicode ):
            if not pattern.search(note_last['content']):
              all_found = False
              break
          else:
            if note_last['content'].lower().find(pattern.lower()) < 0:
              all_found = False
              break
        if all_found:
          results.append(note_last)
      else:
        results.append(note_last)
        
    results.sort(self.cmp_note_by_head)

    if keywords:
      for note in results:
        note['weight'] = self.get_weight(pattern, note)

      results.sort(self.cmp_note_by_weight, reverse=True)
      
      for result in results:
        del result['weight']
        
    return results

  def get_weight(self, pattern, note):
    weight = 0
    break_point = 10000
    lines = note['content'].split('\n')
    head = lines[0]
    tail_index = note['content'].find(head) + len(head)
    tail = note['content'][tail_index:]
    
    isString = type(pattern) is str or type(pattern) is unicode
    if not isString:
      inHead = pattern.search(head)
    else:
      inHead = head.find(pattern) > 0
    if inHead:
      weight += break_point
    
    if not isString:
      groups = pattern.findall(tail)
    else:
      groups = head.split(pattern)
      
    if groups:
      if len(groups) >= break_point:
        weight += break_point - 1
      else:
        weight += len(groups)
      
    return weight
        
        
  def cmp_note_by_weight(self, note1, note2):
    return cmp(note1['weight'], note2['weight'])

  def cmp_note_by_head(self, note1, note2):
    return cmp(note1['content'].split('\n')[0], note2['content'].split('\n')[0])

  def edit(self, id, content):
    note = self.session.query(Note).get(id)
    
    note.pages.append(Page(content))
    self.session.save(note)
    self.session.flush()
    return self.humanize(note)

  def history(self, id, revision = -1):
    note = self.session.query(Note).get(id)
    if revision > 0:
      return self.revisions(note)[-revision]
    elif revision == 0:
      return self.revisions(note, current = True)
    else:
      return self.revisions(note)


  def rollback(self, id, revision):
    revisions = self.history(id)
    content = revisions[len(revisions)-revision]['content']
    return self.edit(id, content)

  def remove(self, id):
    note = self.session.query(Note).get(id)
    note.trashed = 1
    self.session.save(note)
    self.session.flush()

  def clean_trash(self):
    notes = self.session.query(Note).filter_by(trashed=1)
    for note in notes:
      self.session.delete(note)

    self.session.flush()
